﻿using OpenCvSharp;
using OpenCvSharp.WpfExtensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;

namespace OpenCvSharpExtension
{
    public class VideoComponent : System.Windows.DependencyObject, IDisposable
    {
        /// <param name="videoPath">相机索引</param>
        /// <param name="frameWidth">相机画面最大宽度</param>
        /// <param name="frameHeight">相机画面最大高度</param>
        public VideoComponent(string videoPath)
        {
            if (!System.IO.File.Exists(videoPath))
            {
                throw new Exception($"video file not exists:{videoPath}");
            }
            this.VideoPath = videoPath;
            this.capture = new VideoCapture(videoPath);
            this.interval = oneSec / this.capture.Fps;
            /*https://www.fourcc.org/codecs.php
             CV_FOURCC('P', 'I', 'M', '1') = MPEG-1 codec PIM1
            CV_FOURCC('M', 'J', 'P', 'G') = motion-jpeg codec  MJPG
            CV_FOURCC('M', 'P', '4', '2') = MPEG-4.2 codec  MP42
            CV_FOURCC('D', 'I', 'V', '3') = MPEG-4.3 codec  DIV3
            CV_FOURCC('D', 'I', 'V', 'X') = MPEG-4 codec   DIVX
            CV_FOURCC('U', '2', '6', '3') = H263 codec U263
            CV_FOURCC('I', '2', '6', '3') = H263I codec    I263
            CV_FOURCC('F', 'L', 'V', '1') = FLV1 codec     FLV1
            CV_FOURCC('A', 'V', 'C', '1') =  H264 codec    AVC1
             
             */
             var r1 = capture.Set(VideoCaptureProperties.FourCC, FourCC.FromString("MJPG").Value);

            capture.ConvertRgb = false;
            //var qqq = capture.Set(VideoCaptureProperties.Channel, -1);
            //var r2 = capture.Set(VideoCaptureProperties.ConvertRgb, 0d);
            //var w2 = capture.Get(VideoCaptureProperties.ConvertRgb);
            //var r3 = capture.Set(VideoCaptureProperties.PosFrames, 0);

            this.frameMat = new Mat(capture.FrameHeight, capture.FrameWidth, MatType.CV_8UC4);
            thread = new Thread(captureWhile);
            thread.Start();
        }

        private readonly Thread thread;
        private readonly VideoCapture capture;

        private DateTime lastTime;//上一帧的时间
        private TimeSpan interval;//每一帧的间隔时间

        private int curFrame = 0;

        /// <summary>
        /// 可用于在捕获画面的过程中动态调整相机属性
        /// </summary>
        public VideoCapture VideoCapture { get { return capture; } }

        /// <summary>
        /// 图像翻转，<see cref="FlipMode.X"/>沿X轴翻转，<see cref="FlipMode.Y"/>沿Y轴翻转。
        /// </summary>
        public FlipMode? FlipMode { get; set; }

        /// <summary>
        /// 相机索引
        /// </summary>
        public string VideoPath { get; private set; }


        /// <summary>
        /// fps
        /// </summary>
        public double FPS { get => capture.Fps; }
        /// <summary>
        /// 渲染FPS信息
        /// </summary>
        public bool DrawFPS { get; set; } = false;
        /// <summary>
        /// 当前帧索引
        /// </summary>
        public int CurrentFrameIndex { get => curFrame; }
        /// <summary>
        /// 当前视频的帧数量
        /// </summary>
        public int FramesCount { get => capture.FrameCount; }
        /// <summary>
        /// 获取当前位图
        /// </summary>
        public WriteableBitmap Bitmap
        {
            get => bitmap;
        }
        /// <summary>
        /// 原始帧图像（OpenCV格式）
        /// </summary>
        public Mat FrameMat
        {
            get => frameMat;
        }


        private WriteableBitmap bitmap;
        private WriteableBitmap take = null;
        private bool istake = false;
        private bool taskIsReturn = false;//任务是否已退出
        private bool isplay = false;
        /// <summary>
        /// 播放
        /// </summary>
        /// <returns></returns>
        public void Play()
        {
            isplay = true;
        }
        /// <summary>
        /// 暂停
        /// </summary>
        public void Pause()
        {
            isplay = false;
        }
        /// <summary>
        /// 停止
        /// </summary>
        public void Stop()
        {
            isplay = false;
            curFrame = 0;
            capture.PosFrames = 0;
            this.bitmap = null;
            this.OnBitmapChanged();
        }
        /// <summary>
        /// 自动重播
        /// </summary>
        public bool AutoReplay { get; set; } = true;

        /// <summary>
        /// 捕获下一帧画面
        /// </summary>
        /// <returns></returns>
        public async Task<WriteableBitmap> Capture()
        {
            istake = true;
            while (istake)
            {
                await Task.Delay(10);
            }
            return take;
        }

        private void captureWhile()
        {
            while (!isdisposed)
            {
                render();
            }
            taskIsReturn = true;//任务已退出
        }
        private static TimeSpan oneSec = TimeSpan.FromSeconds(1);
        private readonly Mat frameMat;
        private void render()
        {
            if (capture == null || capture.IsDisposed)
            {
                return;
            }
            if (!isplay)
            {
                Thread.Sleep(10);
                return;
            }
            var now = DateTime.Now;
            var t = now - lastTime;
            if (t < interval)
            {
                return;
            }
            lastTime = now;

            var grabbed = capture.Read(frameMat);
            //var grabbed = capture.Retrieve(frameMat);
            if (!grabbed || frameMat.Empty())
            {
                take = null;
                istake = false;
                if (!this.AutoReplay)
                {//自动重播时不修改位图
                    this.Stop();
                }
                else
                {
                    curFrame = 0;
                    capture.PosFrames = 0;
                    Play();
                }

                this.OnMediaEnded();
                return;
            }
            curFrame++;
            if (FlipMode.HasValue)
            {
                Cv2.Flip(frameMat, frameMat, this.FlipMode.Value);
            }

            if (DrawFPS)
            {
                Cv2.PutText(frameMat, $"{oneSec / t:0}", new Point(20, 30), HersheyFonts.HersheyComplex, 1, Scalar.Green, 1);
            }

            if (istake)
            {
                take = this.Dispatcher.Invoke(() => WriteableBitmapConverter.ToWriteableBitmap(frameMat));
                istake = false;
            }

            if (bitmap == null)
            {//当前没有图像或图像尺寸不一致
                this.bitmap = this.Dispatcher.Invoke(() => WriteableBitmapConverter.ToWriteableBitmap(frameMat));
                this.OnBitmapChanged();
            }
            else
            {
                this.Dispatcher.BeginInvoke(new Action(delegate
                {
                    if (this.bitmap != null)
                    {
                        WriteableBitmapConverter.ToWriteableBitmap(frameMat, this.bitmap);
                    }
                }));

            }
            this.OnGotFrame();
        }

        private bool isdisposed = false;//已释放
        public void Dispose()
        {
            isdisposed = true;
            while (!taskIsReturn)
            {
                Thread.Sleep(20);
            }
            capture.Release();
        }

        /// <summary>
        /// 在当前位图指针发生变化时触发（位图的实际内容变化时并不触发）
        /// </summary>
        public event EventHandler BitmapChanged;

        private void OnBitmapChanged()
        {
            try
            {
                if (this.BitmapChanged != null)
                {
                    this.Dispatcher.Invoke(delegate
                    {
                        this.BitmapChanged.Invoke(this, EventArgs.Empty);
                    });
                }

            }
            catch (Exception)
            {
                throw;
            }

        }
        /// <summary>
        /// 获取到新的帧时触发
        /// </summary>
        public event EventHandler GotFrame;
        private void OnGotFrame()
        {
            try
            {
                this.GotFrame?.Invoke(this, EventArgs.Empty);
            }
            catch (Exception)
            {
                throw;
            }

        }

        public event EventHandler MediaEnded;
        private void OnMediaEnded()
        {
            try
            {
                if (this.MediaEnded != null)
                {
                    this.Dispatcher.Invoke(delegate
                    {
                        this.MediaEnded.Invoke(this, EventArgs.Empty);
                    });
                }
            }
            catch (Exception)
            {
                throw;
            }

        }
    }
}
